/*
 * Copyright 2013, NuzoweR@Schneefeuer <nuzower@gmail.com>.
 * The Settlers Online - Combat Simulator (tsosim).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "Units.h"
#include "Utils.h"
#include <sstream>


/* ***************************************** */

/*! This is a prefix map. 
 *  If a string key is used which is prefix of a key that is already in the map, then the other value will be returned. 
 *  If a string key is used and a prefix of that string was already used as a key, then also the the other value will be returned.
 *  User has to make sure that he checks if the value of a prefix is returned before accidentally overwriting something.
 */
typedef std::map<std::string, int, utils::str_less> UnitnameMap_t;
typedef std::map<int, Unit>						    UnitsMap_t;

static UnitnameMap_t    unitnamemap;
static UnitsMap_t       unitmap;

/* ***************************************** */

Unit::Unit()
: name_id(""),
  name(""),
  _name_utf8_length(0),
  hitpoints(0),
  min_damage(0),
  max_damage(0),
  accuracy(0),
  initiative(I_NORMAL),
  skills(S_NONE) ,
  attack_id(-1)
{};


bool Unit::has(std::string const& name_id)
{
	return unitnamemap.find(name_id) != unitnamemap.end();
}

Unit* Unit::get(std::string const& name_id, bool force_name_id)
{
	UnitnameMap_t::iterator iter = unitnamemap.find(name_id);
	if(iter != unitnamemap.end())
	{
		// found unit
		Unit* unit = &unitmap[iter->second];

		if(force_name_id && unit->name_id != name_id)
		{
			return NULL;
		}

		return unit;
	}
	return NULL;
}

Unit* Unit::create(std::string const& name_id, std::string const& name, std::string& error)
{
	static int s_unit_id = 1000;

	// check if 'name_id' is already used or an ambiguous prefix
	{
		UnitnameMap_t::iterator iter = unitnamemap.find(name_id);
		if(iter != unitnamemap.end())
		{
			if(iter->first == name_id) 
				error = "unit with ID '" + name_id + "' already defined";
			else 
				error = "ambiguous prefix for unit names : '" + name_id + "' and '" + iter->first + "'";
		
			return NULL;
		}
	}

	// check if 'name' is already used or an ambiguous prefix
	if(!name.empty())
	{
		UnitnameMap_t::iterator iter = unitnamemap.find(name);
		if(iter != unitnamemap.end())
		{
			if(iter->first == name) 
				error = "unit with name '" + name + "' already defined";
			else 
				error = "ambiguous prefix for unit names : '" + name + "' and '" + iter->first + "'";
		
			return NULL;
		}
	}

	// check if 'name_id' and 'name' have a prefix collision
	if( (name_id != name) && !(utils::compare_string_prefix(name_id, name) || utils::compare_string_prefix(name, name_id)) )
	{
		error = "ambiguous prefix for unit names : '" + name_id + "' and '" + name + "'";
		return NULL;
	}

	int unit_id = s_unit_id++;

	Unit* unit = &unitmap[unit_id];
	unitnamemap[name_id] = unit_id;

	unit->name_id = name_id;

	if(name.empty()) {
		unit->name = unit->name_id;
	}
	else if(name_id == name) {
		unit->name = name;
	}
	else {
		unit->name = name;
		unitnamemap[name] = unit_id;
	}
	
	unit->_name_utf8_length = utils::UTF8LEN(unit->name);

	return unit;
}

bool Unit::set_name(std::string const& name_id, std::string const& name, std::string& error)
{
	Unit* unit = NULL;
	int   unit_id = -1;

	// 0. find unit for name_id
	UnitnameMap_t::iterator it = unitnamemap.find(name_id);
	if(it != unitnamemap.end())
	{
		unit_id = it->second;
		unit    = &unitmap[unit_id];

		if(unit && unit->name_id != name_id)
		{
			error = "found unit, but their ID's don't match exactly : '" + unit->name_id + "', '" + name_id + "'";
			return false;
		}
	}

	if(!unit) {
		error = "unit with ID '" + name_id +"' does not exist";
		return false;
	}

	if(unit->name == name)
	{
		// name has not changed -> abort successfully
		return true;
	}

	// 1. make sure new name is not already in use
	if(!name.empty())
	{
		UnitnameMap_t::const_iterator iter = unitnamemap.find(name);
		if(iter != unitnamemap.end())
		{
			// is in use
			const Unit* testunit = &unitmap[iter->second];
			if( testunit && testunit->name_id != name_id )
			{
				// different unit
				error = "a unit with name '" + name + "' already exists (" + testunit->name_id + ", " + testunit->name + ")";
				return false;
			}

			// same unit. name might be the same or a prefix variation -> might need to replace the map entries
		}
	}

	// 2. remove old unit name from the map
	if(!unit->name.empty())
	{
		UnitnameMap_t::iterator iter = unitnamemap.find(unit->name);
		if(iter != unitnamemap.end()) 
		{
			// check, if name and name_id point to the same unit
			if(unit != &unitmap[iter->second]) {
				error = "nooooooooooooooo!!";
				return false;
			}

			unitnamemap.erase(iter);

			unit->name = unit->name_id;
		}
	}

	// 3. add new name to the map
	if(!name.empty())
	{
		// checked in 1. if new name is already in the map. if it is and belongs to a different unit, then we returned in 1.
		// if it belongs to the same unit, but is a prefix variation, then it was removed in 2.
		// if it belongs to the same unit and is equal to the previous unit name, then we wouldn't be here anyway

		unit->name = name;
		unitnamemap[name] = unit_id;
	}

	unit->_name_utf8_length = utils::UTF8LEN(unit->name);

	return true;
}

const Unit* Unit::find(std::string const& name, std::string& error)
{
	UnitnameMap_t::const_iterator iter = unitnamemap.find(name);
	if(iter == unitnamemap.end()) {
		error = "undefined unit '" + name + "'";
		return NULL;
	}

	if(name != iter->first) 
	{
		// prefix match -> check if there are multiple matches
		std::stringstream ss;
		ss << "ambiguous prefix '" << name << "' matches with :";

		int counter = 0;
		UnitnameMap_t::const_iterator cit = unitnamemap.begin();
		for(; cit != unitnamemap.end(); ++cit)
		{
			if(!utils::compare_string_prefix(name, cit->first) && !utils::compare_string_prefix(cit->first, name))
			{
				counter++;					
				ss << " '" << cit->first << "'";
			}
		}
		if(counter > 1) {
			error = ss.str();
			return NULL;
		}
	}

	return &unitmap[iter->second];
}

